package com.mycode.chapter03.processor.impl;

import com.mycode.chapter03.connector.http.*;
import com.mycode.chapter03.stream.SocketInputStream;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.StringManager;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class HttpProcessor{

    private HttpConnector httpConnector;
    private HttpRequest request;
    private HttpResponse response;
    private HttpRequestLine requestLine = new HttpRequestLine();

    public HttpProcessor(HttpConnector httpConnector) {
        this.httpConnector = httpConnector;
    }

    // Http 包的日志管理
    protected StringManager sm = StringManager.getManager(Constants.Package);

    // 处理 socket
    public void process(Socket socket) {
        SocketInputStream input = null;
        OutputStream output = null;
        try {
            // 获取 Socket 输入输出流
            input = new SocketInputStream(socket.getInputStream(), 2048);
            output = socket.getOutputStream();

            // 创建 HttpRequest 实例
            request = new HttpRequest(input);

            // 创建 HttpResponse 实例
            response = new HttpResponse(output);
            response.setRequest(request);

            // 设置响应头信息
            response.setHeader("Server", "Pyrmont Servlet Container");

            // 解析 Request
            // 只解析第一行 method uri protocol
            parseRequest(input, output);
            // 解析 请求头 Headers
            parseHeaders(input);

            // 根据uri 判断是需要静态资源 还是动态资源
            if (request.getRequestURI().startsWith("/servlet/")) {
                ServletProcessor processor = new ServletProcessor();
                // 处理动态资源
                processor.process(request, response);
            }
            else {
                StaticResourceProcessor processor = new StaticResourceProcessor();
                // 处理静态资源
                processor.process(request, response);
            }

            // Close the socket
            socket.close();
            // no shutdown for this application
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 解析 请求头信息
    private void parseHeaders(SocketInputStream input) throws IOException, ServletException {
        while (true) {
            // 创建 HttpHeader 实例
            HttpHeader header = new HttpHeader();;

            // 读取头信息
            input.readHeader(header);
            if (header.nameEnd == 0) {
                if (header.valueEnd == 0) {
                    return;
                }
                else {
                    throw new ServletException(sm.getString("httpProcessor.parseHeaders.colon"));
                }
            }

            // 获取请求头的名字和值
            String name = new String(header.name, 0, header.nameEnd);
            String value = new String(header.value, 0, header.valueEnd);
            // 添加到 request 的 headers
            request.addHeader(name, value);
            // 解析 Cookie
            if (name.equals("cookie")) {
                Cookie cookies[] = RequestUtil.parseCookieHeader(value);
                for (int i = 0; i < cookies.length; i++) {
                    if (cookies[i].getName().equals("jsessionid")) {
                        // Override anything requested in the URL
                        if (!request.isRequestedSessionIdFromCookie()) {
                            // Accept only the first session id cookie
                            request.setRequestedSessionId(cookies[i].getValue());
                            request.setRequestedSessionCookie(true);
                            request.setRequestedSessionURL(false);
                        }
                    }
                    request.addCookie(cookies[i]);
                }
            }
            else if (name.equals("content-length")) {
                int n = -1;
                try {
                    n = Integer.parseInt(value);
                }
                catch (Exception e) {
                    throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
                }
                request.setContentLength(n);
            }
            else if (name.equals("content-type")) {
                request.setContentType(value);
            }
        } //end while
    }

    // 解析 Request 对象 解析 HTTP 协议
    private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException {
        // 使用 SocketInputStream 对象中的信息填充 HttpRequestLine
        input.readRequestLine(requestLine);
        // 获取请求方法
        String method = new String(requestLine.method, 0, requestLine.methodEnd);
        // URI
        String uri = null;
        // 请求协议的版本信息
        String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

        if (method.length() < 1) {
            throw new ServletException("Missing HTTP request method");
        }
        else if (requestLine.uriEnd < 1) {
            throw new ServletException("Missing HTTP request URI");
        }
        // URI后面是否有查询条件
        int question = requestLine.indexOf("?");
        if (question > 0) {
            // 获取查询字符串，并填充 HttpRequest 对象
            request.setQueryString(new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1));
            uri = new String(requestLine.uri, 0, question);
        }
        else {
            request.setQueryString(null);
            uri = new String(requestLine.uri, 0, requestLine.uriEnd);
        }

        // 判断 URI 是相对路径 还是绝对路径
        if (!uri.startsWith("/")) {
            int pos = uri.indexOf("://");
            if (pos != -1) {
                pos = uri.indexOf("/", pos + 3);
                if (pos == -1) {
                    uri = "";
                }
                else {
                    uri = uri.substring(pos);
                }
            }
        }

        // 判断是否包含会话标识符（session）
        String match = ";jsessionid=";
        int semicolon = uri.indexOf(match);
        if (semicolon >= 0) {
            String rest = uri.substring(semicolon + match.length());
            int semicolon2 = rest.indexOf(";");
            if (semicolon2 >= 0) {
                request.setRequestedSessionId(rest.substring(0, semicolon2));
                rest = rest.substring(semicolon2);
            } else {
                request.setRequestedSessionId(rest);
                rest = "";

            }
            request.setRequestedSessionURL(true);
            uri = uri.substring(0, semicolon) + rest;
        }
        else {
            request.setRequestedSessionId(null);
            request.setRequestedSessionURL(false);
        }

        // 对非正常的 URI 进行修正，比如 \ 转换为 /
        String normalizedUri = normalize(uri);
        // 设置 HttpRequest 属性
        request.setMethod(method);
        request.setProtocol(protocol);
        if (normalizedUri != null) {
            request.setRequestURI(normalizedUri);
        }
        else {
            request.setRequestURI(uri);
        }

        if (normalizedUri == null) {
            // 修正后的 URI 无效
            throw new ServletException("Invalid URI: " + uri + "'");
        }

    }

    private String normalize(String path) {
        if (path == null)
            return null;
        // Create a place for the normalized path
        String normalized = path;

        // Normalize "/%7E" and "/%7e" at the beginning to "/~"
        if (normalized.startsWith("/%7E") || normalized.startsWith("/%7e")) {
            normalized = "/~" + normalized.substring(4);
        }

        // Prevent encoding '%', '/', '.' and '\', which are special reserved
        // characters
        if ((normalized.indexOf("%25") >= 0)
                || (normalized.indexOf("%2F") >= 0)
                || (normalized.indexOf("%2E") >= 0)
                || (normalized.indexOf("%5C") >= 0)
                || (normalized.indexOf("%2f") >= 0)
                || (normalized.indexOf("%2e") >= 0)
                || (normalized.indexOf("%5c") >= 0)) {
            return null;
        }

        if (normalized.equals("/.")) {
            return "/";
        }

        // Normalize the slashes and add leading slash if necessary
        if (normalized.indexOf('\\') >= 0) {
            normalized = normalized.replace('\\', '/');
        }
        if (!normalized.startsWith("/")) {
            normalized = "/" + normalized;
        }

        // Resolve occurrences of "//" in the normalized path
        while (true) {
            int index = normalized.indexOf("//");
            if (index < 0) {
                break;
            }
            normalized = normalized.substring(0, index) +
                    normalized.substring(index + 1);
        }

        // Resolve occurrences of "/./" in the normalized path
        while (true) {
            int index = normalized.indexOf("/./");
            if (index < 0) {
                break;
            }
            normalized = normalized.substring(0, index) +
                    normalized.substring(index + 2);
        }

        // Resolve occurrences of "/../" in the normalized path
        while (true) {
            int index = normalized.indexOf("/../");
            if (index < 0) {
                break;
            }
            if (index == 0) {
                return (null);  // Trying to go outside our context
            }
            int index2 = normalized.lastIndexOf('/', index - 1);
            normalized = normalized.substring(0, index2) +
                    normalized.substring(index + 3);
        }

        // Declare occurrences of "/..." (three or more dots) to be invalid
        // (on some Windows platforms this walks the directory tree!!!)
        if (normalized.indexOf("/...") >= 0) {
            return (null);
        }

        // Return the normalized path that we have completed
        return (normalized);
    }
}
